﻿using System.Runtime.CompilerServices;
namespace In66.Net.Caching.Extensions
{
    public static class CachingServiceCollectionExtension
    {
        #region Common

        private static readonly ConcurrentDictionary<string, char> s_RegisteredModels = new();

        public static bool IsCacheNotAbstractClass(this Type type, bool publicOnly)
        {
            if (type.IsSpecialName)
            {
                return false;
            }

            if (type.IsClass && !type.IsAbstract)
            {
                if (type.HasAttribute<CompilerGeneratedAttribute>())
                {
                    return false;
                }

                if (publicOnly)
                {
                    return type.IsPublic || type.IsNestedPublic;
                }

                return true;
            }
            return false;
        }

        public static bool HasAttribute(this Type type, Type attributeType) => type.IsDefined(attributeType, inherit: true);

        public static bool HasAttribute<T>(this Type type) where T : Attribute => type.HasAttribute(typeof(T));

        public static bool HasAttribute<T>(this Type type, Func<T, bool> predicate) where T : Attribute => type.GetCustomAttributes<T>(inherit: true).Any(predicate);

        public static bool HasRegistered(this IServiceCollection _, string modelName) => !s_RegisteredModels.TryAdd(modelName.ToLower(), '1');

        internal static T? GetSingletonInstanceOrNull<T>(this IServiceCollection services)
            where T : class
        {
            var instance = services.FirstOrDefault(d => d.ServiceType == typeof(T))?.ImplementationInstance;
            if (instance is null)
            {
                return null;
            }

            return (T)instance;
        }

        internal static T GetSingletonInstance<T>(this IServiceCollection services)
            where T : class
        {
            var instance = GetSingletonInstanceOrNull<T>(services);
            if (instance is null)
            {
                throw new InvalidOperationException("Could not find singleton service: " + typeof(T).AssemblyQualifiedName);
            }

            return instance;
        }

        #endregion

        public static IServiceCollection AddInfraCaching(this IServiceCollection services, IConfigurationSection redisSection)
        {
            if (services.HasRegistered(nameof(AddInfraCaching)))
            {
                return services;
            }

            services
                .Configure<RedisConfig>(redisSection)
                .AddScoped<CachingInterceptor>()
                .AddScoped<CachingAsyncInterceptor>()
                .AddSingleton<ICachingKeyGenerator, DefaultCachingKeyGenerator>();

            var serviceType = typeof(IRedisSerializer);
            var implementations = serviceType.Assembly.ExportedTypes.Where(type => type.IsAssignableTo(serviceType) && type.IsCacheNotAbstractClass(true)).ToList();
            implementations.ForEach(implementationType => services.AddSingleton(serviceType, implementationType));

            var redisConfig = redisSection.Get<RedisConfig>();
            switch (redisConfig.Provider)
            {
                case RedisConstValue.Provider.StackExchange:
                    AddStackExchange(services);
                    break;
                case RedisConstValue.Provider.ServiceStack:
                    break;
                case RedisConstValue.Provider.FreeRedis:
                    break;
                case RedisConstValue.Provider.CSRedis:
                    break;
                default:
                    throw new NotSupportedException(nameof(redisConfig.Provider));
            }
            return services;
        }

        /// <summary>
        /// AddStackExchange
        /// </summary>
        /// <param name="services"></param>
        /// <returns></returns>
        public static IServiceCollection AddStackExchange(IServiceCollection services)
        {
            return
                services
                .AddSingleton<DefaultDatabaseProvider>()
                .AddSingleton<DefaultRedisProvider>()
                .AddSingleton<ICacheProvider, CachingProvider>()
                .AddSingleton<IRedisProvider>(x => x.GetRequiredService<DefaultRedisProvider>())
                .AddSingleton<IDistributedLocker>(x => x.GetRequiredService<DefaultRedisProvider>());
        }
    }
}
